package com.xnxkj.wx.micro.mch.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.xnxkj.wx.micro.mch.bean.message.request.RequestMessage;
import com.xnxkj.wx.micro.mch.bean.message.request.StateMessage;
import com.xnxkj.wx.micro.mch.bean.message.response.*;
import com.xnxkj.wx.micro.mch.service.IWxMicroMchService;
import com.xnxkj.wx.micro.mch.bean.message.request.SubmitMessage;
import com.xnxkj.wx.micro.mch.bean.WxMerchantConfig;
import com.xnxkj.wx.micro.mch.excaption.WxMicroMchException;
import com.xnxkj.wx.micro.mch.excaption.WxMessageProcessingException;
import com.xnxkj.wx.micro.mch.excaption.WxNetworkException;
import com.xnxkj.wx.micro.mch.excaption.WxSignCheckFailException;
import com.xnxkj.wx.micro.mch.utils.SignUtils;
import okhttp3.*;
import org.apache.commons.codec.digest.DigestUtils;

import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.*;

public class WxMicroMchServiceImpl implements IWxMicroMchService {
    private static final MediaType MEDIA_TYPE_XML = MediaType.parse("text/xml");
    private WxMerchantConfig wxMerchantConfig;
    private OkHttpClient defaultHttpClient;
    private OkHttpClient sslHttpClient;
    private XmlMapper xmlMapper;

    @SuppressWarnings("WeakerAccess")
    public WxMicroMchServiceImpl(WxMerchantConfig wxMerchantConfig) {
        this.wxMerchantConfig = wxMerchantConfig;
        defaultHttpClient = new OkHttpClient();
        sslHttpClient = new OkHttpClient.Builder().sslSocketFactory(wxMerchantConfig.getSslContext().getSocketFactory(), new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}

            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}

            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }).build();
        xmlMapper = new XmlMapper();
    }

    public CertificatesResultMessage getCertificates() throws WxMicroMchException {
        SortedMap<String, String> paramMap = new TreeMap<>();
        paramMap.put("mch_id", wxMerchantConfig.getMchId());
        SignUtils.generateSign(paramMap, wxMerchantConfig.getKey(), SignUtils.SignType.HMAC_SHA256);
        Request request;
        try {
            request = new Request.Builder()
                    .url(URL_HOST + URL_GET_CERTIFICATES)
                    .post(RequestBody.create(MEDIA_TYPE_XML, xmlMapper.writeValueAsString(paramMap)))
                    .build();
        } catch (JsonProcessingException e) {
            throw new WxMessageProcessingException("message=" + paramMap, e);
        }
        try(Response response = defaultHttpClient.newCall(request).execute()) {
            return getResponseMessage(response, SignUtils.SignType.HMAC_SHA256, CertificatesResultMessage.class);
        } catch (IOException e) {
            throw new WxMicroMchException(e);
        }
    }

    @Override
    public MediaUploadResultMessage uploadMedia(String filename, MediaType mediaType, byte[] mediaContent) throws WxMicroMchException {
        SortedMap<String, String> signComputeMap = new TreeMap<>();
        String mediaHash = DigestUtils.md5Hex(mediaContent);
        signComputeMap.put("mch_id", wxMerchantConfig.getMchId());
        signComputeMap.put("media_hash", mediaHash);
        signComputeMap.put("sign_type", SignUtils.SignType.MD5);
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("mch_id", wxMerchantConfig.getMchId())
                .addFormDataPart("media_hash", mediaHash)
                .addFormDataPart("sign_type", SignUtils.SignType.MD5)
                .addFormDataPart("sign", SignUtils.getSign(signComputeMap, wxMerchantConfig.getKey(), SignUtils.SignType.MD5))
                .addFormDataPart("media", filename, RequestBody.create(mediaType, mediaContent))
                .build();
        Request request = new Request.Builder().url(URL_HOST + URL_UPLOAD_MEDIA).post(requestBody).build();
        try(Response response = sslHttpClient.newCall(request).execute()){
            return getResponseMessage(response, SignUtils.SignType.MD5, MediaUploadResultMessage.class);
        }catch (IOException e){
            throw new WxNetworkException(e);
        }
    }

    @Override
    public SubmitResultMessage submit(SubmitMessage message) throws WxMicroMchException {
        message.setMchId(wxMerchantConfig.getMchId());
        return sendMessage(URL_SUBMIT, message, SignUtils.SignType.HMAC_SHA256, SubmitResultMessage.class);
    }

    @Override
    public StateResultMessage getState(StateMessage stateMessage) throws WxMicroMchException {
        stateMessage.setMchId(wxMerchantConfig.getMchId());
        stateMessage.setVersion("1.0");
        return sendMessage(URL_GET_STATE, stateMessage, SignUtils.SignType.HMAC_SHA256, StateResultMessage.class);
    }

    /**
     * 发送消息
     * @param url 消息地址（不包含Host）
     * @param message 消息内容
     * @param signType 消息签名类型
     * @param responseClass 返回消息类型
     */
    private <T extends ResponseMessage> T sendMessage(String url, RequestMessage message, String signType, Class<T> responseClass) throws WxMicroMchException{
        SignUtils.generateSign(message, wxMerchantConfig.getKey(), signType);
        RequestBody requestBody;
        try {
            requestBody = RequestBody.create(MEDIA_TYPE_XML, xmlMapper.writeValueAsString(message));
        } catch (JsonProcessingException e) {
            throw new WxMessageProcessingException(e);
        }
        Request request = new Request.Builder().url(URL_HOST + url).post(requestBody).build();
        try(Response response = sslHttpClient.newCall(request).execute()){
            return getResponseMessage(response, signType, responseClass);
        }catch (IOException e){
            throw new WxNetworkException(e);
        }
    }

    /**
     * 获取请求内容并检查错误
     * @param response 响应内容
     * @param signType 签名类型
     * @param clazz 返回值类型
     * @param <T> 返回值泛型
     */
    private <T extends ResponseMessage> T getResponseMessage(Response response, String signType, Class<T> clazz)
            throws WxNetworkException, IOException, WxSignCheckFailException, WxMessageProcessingException {
        ResponseBody responseBody = Optional.ofNullable(response.body()).orElseThrow(()->new WxNetworkException("响应内容为空"));
        if(!response.isSuccessful()) {
            throw new WxNetworkException("请求失败：code=" + response.code() + "\tmessage=" + response.message() + "\tresponseContent=" + responseBody.string());
        }
        String responseContent = responseBody.string();
        T result;
        try {
            result = xmlMapper.readValue(responseContent, clazz);
        }catch (IOException e){
            throw new WxMessageProcessingException("解析数据失败\tresponseContent=" + responseContent, e);
        }
        String sign = SignUtils.getSign(result.toMap(), wxMerchantConfig.getKey(), signType);
        if(!sign.equals(result.getSign())){
            throw new WxSignCheckFailException("responseSign=" + result.getSign() + "\tcomputeSign=" + sign + "\tresponseContent=" + responseContent);
        }
        if(result.getReturnCode().equals("FAIL")){
            throw new WxNetworkException(result.getReturnMsg());
        }
        if(!"SUCCESS".equals(result.getResultCode())){
            throw new WxNetworkException("err_code=" + result.getErrorCode() + "\terr_code_des=" + result.getErrorCodeDescribe());
        }
        return result;
    }
}
